UnLua & C++ 交互
在 Lua
中调用一般的 UFUNCTION
反射比较简单直接,这里以从 Lua
中调用 CFUNCTION
为例简单介绍两端交互。
具体为在 Lua
中的调用:local Table = UE4.UTestUtils.LuaCFunction(Val0, Val1)
,并返回一个 Lua Table : { "Val0" = Val0, "Val1" = Va1 }
。
定义
首先通过 ADD_STATIC_CFUNCTION
静态导出这个原生的 FGlueFunction
;
具体的实现可以在 UnLuaEx.h 中找到。
1 2 3 4 5 6 7 8 9 10 11
| struct lua_State;
UCLASS() class UTestUtils : public UBlueprintFunctionLibrary { GENERATED_BODY()
public: static int LuaCFunction(lua_State* L); }
|
1 2 3 4 5
| BEGIN_EXPORT_REFLECTED_CLASS(UTestUtils) ADD_STATIC_CFUNTION(LuaCFunction) END_EXPORT_CLASS(UTestUtils) IMPLEMENT_EXPORTED_CLASS(UTestUtils)
|
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int UTestUtils::LuaCFunction(lua_State* L) { const int Top = lua_gettop(L); if (Top == 2) { int32 Val0 = lua_tointeger(L, 1); int32 Val1 = lua_tointeger(L, 2); UUnLuaFunctionLibrary::CreateLuaTable(L, "Val0", Val0, "Val1", Val1); return 1; }
luaL_error(L, "Call UTestUtils::LuaCFunction error! argc = %d", Top); return 0; }
|
CFUNCTION
在 Lua
中调用时,会传入当前的 lua_State* L
,可以通过这个L
对 LuaStack
进行访问与写入。
首先这里的 Top = lua_gettop(L)
会根据 cast_int(L->top - (L->ci->func + 1))
计算出返回参数的个数,比如这里的 (Val0, Val1)
就是 2
个参数。
接着通过 lua_tointeger(L, i)
将第 i
个参数取出。
然后通过 UUnLuaFunctionLibrary::CreateLuaTable
,创建一个 Lua Table
,进行赋值与写入,这个方法是实现的重点。
最后如果合法,return 1
,返回写入 LuaStack
中返回值的个数,也就是有 1
个 LuaTable
被写入了栈。
具体的与 lua
层交互的原始方法实现可以在 lapi.c 中找到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| template<typename... Types> UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaTable(lua_State* L, Types&&... Args) { const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L); lua_newtable(L);
PushKeyValue(L, Forward<Types>(Args)...);
return UnLua::FLuaTable(LuaEnv, -1); }
template<typename K, typename V, typename... Types> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value, Types&&... Args) { PushKeyValue(L, Forward<K>(Key), Forward<V>(Value)); PushKeyValue(L, Forward<Types>(Args)...); }
template<typename K, typename V> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value) { UnLua::Push(L, Forward<K>(Key)); UnLua::Push(L, Forward<V>(Value)); lua_rawset(L, -3); }
|
这个 UUnLuaFunctionLibrary::CreateLuaTable
向 L
对应的 LuaStack
中写入了一个赋值好的 LuaTable
。
首先通过 lua_newtable(L)
创建了一个空的 LuaTable
,然后进行 PushKeyValue
的递归调用。
针对一次 PushKeyValue
,首先会往 LuaStack
中压入 Key
、Value
,压入后先前的 LuaTable
处于在 StatckIndex=-3
的位置。
然后针对 StackIndex = -3
位置(也就是当前栈里的 LuaTable
)调用 lua_rawset
方法,将 Key
和 Value
弹出并打包成参数塞进 LuaTable
。
这里的 lua_rawset
实际上类似 lua_settable
。对于 lua_settable
,会找到元方法 __newindex
并调用,对于 lua_rawset
,则会调用默认的 __newindex
方法:
1 2 3
| __newindex = function(table, key, value) rawset(table, key, value) end
|
这样,就完成了一个这样的方法实现。
给出 UUnluaFunctionLibrary
的更多扩展:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
template<typename... Types> UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaTable(lua_State* L, Types&&... Args) { const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L); lua_newtable(L);
PushKeyValue(L, Forward<Types>(Args)...);
return UnLua::FLuaTable(LuaEnv, -1); }
template<typename... Types> UnLua::FLuaTable UUnLuaFunctionLibrary::CreateLuaArray(lua_State* L, const Types&... Args) { const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L); lua_newtable(L);
PushKeyValue<1>(L, Forward<Types>(Args)...);
return UnLua::FLuaTable(LuaEnv, -1); }
template<typename K, typename V> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value) { UnLua::Push(L, Forward<K>(Key)); UnLua::Push(L, Forward<V>(Value)); lua_rawset(L, -3); }
template<typename K, typename V, typename... Types> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, V&& Value, Types&&... Args) { PushKeyValue(L, Forward<K>(Key), Forward<V>(Value)); PushKeyValue(L, Forward<Types>(Args)...); }
template<int N, typename T> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, T&& Arg) { UnLua::Push(L, N); UnLua::Push(L, Forward<T>(Arg)); lua_rawset(L, -3); }
template<int N, typename T, typename... Types> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, T&& Arg0, Types&&... Args) { PushKeyValue<N>(L, Forward<T>(Arg0)); PushKeyValue<N + 1>(L, Forward<Types>(Args)...); }
template<typename K, typename T> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, TArray<T>& Array) { UnLua::Push(L, Forward<K>(Key));
lua_newtable(L); for (int Index = 0; Index < Array.Num(); Index++) { UnLua::Push(L, Index + 1); UnLua::Push(L, Array[Index]); lua_rawset(L, -3); }
lua_rawset(L, -3); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
template <typename T> typename TEnableIf<TIsUStruct<T>::Value, T*>::Type UUnLuaFunctionLibrary::GetValue(lua_State* L, int32 Index) { const auto& Env = UnLua::FLuaEnv::FindEnvChecked(L);
const auto StructType = Env.GetPropertyRegistry()->CreateTypeInterface(L, Index); if (!StructType) return nullptr;
const auto StructProperty = CastField<FStructProperty>(StructType->GetUProperty()); if (StructProperty == nullptr) return nullptr;
if (!StructProperty->Struct->IsChildOf(TBaseStructure<T>::Get())) return nullptr;
return static_cast<T*>(GetCppInstanceFast(L, Index)); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
template<typename K> void UUnLuaFunctionLibrary::PushKeyValue(lua_State* L, K&& Key, const TSharedPtr<FJsonValue>& Value) { UnLua::Push(L, Forward<K>(Key)); PushValue(L, Value); lua_rawset(L, -3); }
void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TSharedPtr<FJsonObject>& JsonObject) { lua_newtable(L); for (const auto& KeyValue : JsonObject->Values) { PushKeyValue(L, KeyValue.Key, KeyValue.Value); } }
void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TArray<TSharedPtr<FJsonValue>>& JsonValues) { lua_newtable(L); for (auto i = 0; i < JsonValues.Num(); i++) { PushKeyValue(L, i + 1, JsonValues[i]); } }
void UUnLuaFunctionLibrary::PushValue(lua_State* L, const TSharedPtr<FJsonValue>& Value) { switch (Value->Type) { case EJson::String: UnLua::Push(L, Value->AsString()); break; case EJson::Number: UnLua::Push(L, Value->AsNumber()); break; case EJson::Boolean: UnLua::Push(L, Value->AsBool()); break; case EJson::Array: PushValue(L, Value->AsArray()); break; case EJson::Object: PushValue(L, Value->AsObject()); break; default: lua_pushnil(L); break; } }
int UUnLuaFunctionLibrary::GetLuaTableFromJsonPath(lua_State* L) { const int Top = lua_gettop(L); if (Top != 1) { LogWarning(TEXT("Args count error. argc = %d"), Top); return 0; }
auto JsonFilePath = FString(UTF8_TO_TCHAR(lua_tostring(L, 1))); if (!FPaths::FileExists(JsonFilePath)) { LogWarning(TEXT("Json file path '%s' not found."), *JsonFilePath); return 0; }
FString FileContent; if (FFileHelper::LoadFileToString(FileContent, *JsonFilePath)) { TSharedPtr<FJsonObject> JsonObject; auto JsonReader = TJsonReaderFactory<>::Create(FileContent); if (FJsonSerializer::Deserialize(JsonReader, JsonObject)) { PushValue(L, JsonObject); } else { LogWarning(TEXT("Parse json from file '%s' failed."), *JsonFilePath); return 0; } } else { LogWarning(TEXT("Load file path '%s' failed."), *JsonFilePath); return 0; }
return 1; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
template <typename... T> UnLua::FLuaRetValues UUnLuaFunctionLibrary::InnerCall(UObjectBaseUtility* Obj, lua_State *L, const char *FuncName, T&&... Args) { const auto LuaEnv = UnLua::FLuaEnv::FindEnv(L); if (UnLua::GetObjectLuaInstance(Obj)) { const UnLua::FLuaTable LuaInstance(UnLua::FLuaValue(LuaEnv, -1)); const UnLua::FLuaFunction LuaFunc(LuaEnv, LuaInstance, FuncName); if (LuaFunc.IsValid()) { auto Ret = LuaFunc.Call(LuaInstance, Forward<T>(Args)...); lua_pop(L, 1); return Ret; } } return UnLua::FLuaRetValues(LuaEnv, 0); }
template <typename... T> UnLua::FLuaRetValues InnerCall(const char *FuncName, T&&... Args) { return UUnLuaFunctionLibrary::InnerCall(this, UnLua::GetState(), FuncName, Args); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
template<typename... T> struct FUnLuaTableCallback { FUnLuaTableCallback(TSharedPtr<UnLua::FLuaTableRef> Table, TSharedPtr<UnLua::FLuaFunction> Func) : Table(Table), Func(Func) {}
inline void operator() (T... Args) { Func->Call(*Table.Get(), Forward<T>(Args)...); }
private: TSharedPtr<UnLua::FLuaTableRef> Table; TSharedPtr<UnLua::FLuaFunction> Func; };
#define DECLARE_MULTICAST_DELEGATE_LUA_HELPER(OwnerType, DelegateName, ...) \ private: \ TMap<FString, FDelegateHandle> DelegateName##LuaHandles; \ public: \ static int Bind##DelegateName(lua_State* L) \ { \ if (!UUnLuaFunctionLibrary::CheckParamsCount(L, 3)) { return luaL_error(L, "Invalid parameters count."); } \ auto Self = static_cast<OwnerType*>(GetCppInstanceFast(L, 1)); \ auto TableValue = UnLua::FLuaValue(L, 2); \ if (TableValue.GetType() != LUA_TTABLE) { return luaL_error(L, "Parameter 2 is invalid; it should be a table."); } \ auto Key = UUnLuaFunctionLibrary::ToString(L, 2); \ if (Self->DelegateName##LuaHandles.Contains(Key)) { return 0; } \ auto FuncValue = UnLua::FLuaValue(L, 3); \ if (FuncValue.GetType() != LUA_TFUNCTION) { return luaL_error(L, "Parameter 3 is invalid; it should be a function."); } \ TSharedPtr<UnLua::FLuaTableRef> Table = MakeShareable(new UnLua::FLuaTableRef(L, TableValue)); \ TSharedPtr<UnLua::FLuaFunction> Func = MakeShareable(new UnLua::FLuaFunction(L, FuncValue)); \ Self->DelegateName##LuaHandles.Emplace(Key, Self->DelegateName.AddLambda(FUnLuaTableCallback<__VA_ARGS__>(Table, Func))); \ return 0; \ } \ static int Unbind##DelegateName(lua_State* L) \ { \ if (!UUnLuaFunctionLibrary::CheckParamsCount(L, 2)) { return luaL_error(L, "Invalid parameters count."); } \ auto Self = static_cast<OwnerType*>(GetCppInstanceFast(L, 1)); \ auto TableValue = UnLua::FLuaValue(L, 2); \ if (TableValue.GetType() != LUA_TTABLE) { return luaL_error(L, "Parameter 2 is invalid; it should be a table."); } \ auto Key = UUnLuaFunctionLibrary::ToString(L, 2); \ if (!Self->DelegateName##LuaHandles.Contains(Key)) { return 0; } \ Self->DelegateName.Remove(Self->DelegateName##LuaHandles[Key]); \ Self->DelegateName##LuaHandles.Remove(Key); \ return 0; \ }
#define ADD_MULTICAST_DELEGATE_LUA_HELPER_FUNC(DelegateName) \ ADD_STATIC_CFUNTION(Bind##DelegateName) \ ADD_STATIC_CFUNTION(Unbind##DelegateName) #pragma endregion Delegate Lua Helper
|
参考
Lua 5.4 Reference: 4.6 – Functions and Types
Lua 虚拟栈交互流程
Unlua 解析